#include "viewer.h"

#include <board.h>
#include <lcd/lcdd.h>
#include <lcd/draw.h>
#include <tsd/tsd.h>
#include <tsd/inarea.h>
#include <pit/pitd.h>

#include <d264.h>

/*******************************************************************************
 * Frames Buffer
 */

#define _NUMOF_FRAMES   7 // must be higher than 1
#define _MIN_BUFFERED   3 // must be at [1;_NUMOF_FRAMES[
static unsigned char _fremas[BOARD_LCD_HEIGHT * BOARD_LCD_WIDTH * 3
        * _NUMOF_FRAMES];
static unsigned char *frames[_NUMOF_FRAMES];

static unsigned char ** volatile curr_frame;
static unsigned char ** volatile last_frame;
static unsigned char ** volatile put_frame;
static unsigned char ** volatile get_frame;

/*******************************************************************************
 * User Interface
 */

#define _EXIT_SIZE 60

static inarea_rect_t exit_ret = { .x = 0, .y = 0, .width = _EXIT_SIZE,
        .height = _EXIT_SIZE };
static inarea_rect_t picture[2] = { { .x = _EXIT_SIZE, .y = 0, .width =
        BOARD_LCD_WIDTH - _EXIT_SIZE, .height = BOARD_LCD_HEIGHT }, { .x = 0,
        .y = _EXIT_SIZE, .width = _EXIT_SIZE, .height = BOARD_LCD_HEIGHT
                - _EXIT_SIZE } };

static void draw_exit(color_t back, color_t front)
{
    LCDD_DrawCircle(*curr_frame, _EXIT_SIZE / 2, _EXIT_SIZE / 2,
            (_EXIT_SIZE - 10) / 2, back);
    LCDD_DrawCircle(*curr_frame, _EXIT_SIZE / 2, _EXIT_SIZE / 2,
            (_EXIT_SIZE - 20) / 2, front);
}

static int _selected;
static volatile int state;
#define _PLAY   1
#define _PAUSE  2
#define _EXIT   3

#define _PAUSE_IF_PLAYING() \
    if (!_selected && state == _PLAY) { \
        state = _PAUSE; \
        return; \
    }

#define _CHECK_SELECTION()  { if (!_selected) return; _selected = 0; }

static void exit_pressed(inarea_rect_t * pRect)
{
    _PAUSE_IF_PLAYING();
    _selected = 1;
    draw_exit(COLOR_BLACK, COLOR_WHITE);
}
static void exit_moved_out(inarea_rect_t * pRect)
{
    _CHECK_SELECTION();
    draw_exit(COLOR_RED, COLOR_TOMATO);
}
static void exit_release(inarea_rect_t * pRect)
{
    _CHECK_SELECTION();
    state = _EXIT;
}

static void v_pause(inarea_rect_t * pRect)
{
    _PAUSE_IF_PLAYING();
    _selected = 1;
}
static void v_play(inarea_rect_t * pRect)
{
    if (_selected && state == _PAUSE) {
        _selected = 0;
        state = _PLAY;
    }
}

static int volatile on_hold = 0;

static void _viewer_handler(void)
{
    static int paused = 0;
    static int buffering = 1;

    if (paused) {
        if (state == _PLAY) {
            paused = 0;
        } else if (state == _PAUSE) {
            goto _touch_handling;
        } else {
            return;
        }
    } else if (state != _PLAY) {
        if (state == _PAUSE) {
            paused = 1;
            draw_exit(COLOR_RED, COLOR_TOMATO);
            goto _touch_handling;
        }
        if (state == _EXIT) {
            return;
        }
    }

    if (buffering) {
        if (curr_frame - last_frame == _NUMOF_FRAMES - _MIN_BUFFERED || last_frame - curr_frame
                == _MIN_BUFFERED || on_hold) {
            buffering = 0;
        }
    } else if (curr_frame == last_frame) {
        if (on_hold)
            return;
        LCDD_DrawRectangle(*curr_frame, 0, 200, BOARD_LCD_WIDTH, 20, COLOR_CYAN);
        LCDD_DrawString(*curr_frame, 42, 203, "> Buffering...", COLOR_INDIGO);
        buffering = 1;
    } else {
        ++get_frame;
        if (get_frame == frames + _NUMOF_FRAMES) {
            get_frame = frames;
        }
        LCDD_DisplayBuffer(*get_frame);
        curr_frame = get_frame;
    }

    _touch_handling: TSD_Update_PenState();
    return;
}

/*******************************************************************************
 * Decoding
 */

static void yuv2rgb_with_rotation(unsigned char* image,
                                  const unsigned char* py,
                                  const unsigned char* pu,
                                  const unsigned char* pv,
                                  const int width,
                                  const int height,
                                  const int y_stride,
                                  const int uv_stride);
static void write_frame(DecodedPicList * frame)
{
    while (put_frame == curr_frame && state != _EXIT)
        ;

    if (state == _EXIT) {
        return;
    }

    yuv2rgb_with_rotation(*put_frame, frame->pY, frame->pU, frame->pV,
            frame->iWidth, frame->iHeight, frame->iYBufStride,
            frame->iUVBufStride);

    // update last frame
    last_frame = put_frame;
    // update put frame
    ++put_frame;
    if (put_frame == frames + _NUMOF_FRAMES) {
        put_frame = frames;
    }
}

/*******************************************************************************
 * Public Functions
 */

static int _decret;
static void _error_func(inarea_rect_t * pRect)
{
    _decret = 0;
}
#define VIEWER_OPEN_ERROR(x,str) { error_x = x; error_str = str; }

int viewer_open(char* filename)
{
    int error_x;
    char *error_str;

    int decod_state = 1;
    int decod_return;
    DecodedPicList *decod_unused; // TODO Remove this?
    InputParameters *pInputParams = &cfgparams; // TODO Remove this?
    int decod_frames = 0;

    unsigned char *oldBuff;

    // Initial black frame.
    curr_frame = frames;
    LCDD_Fill(*curr_frame, COLOR_BLACK);
    oldBuff = LCDD_DisplayBuffer(*curr_frame);
    last_frame = curr_frame;
    get_frame = curr_frame;
    put_frame = frames + 1;

    // Configure the d264 external decoder to use the video file..
    if (Configure(filename, 0, write_frame) || OpenDecoder(pInputParams)) {
        _decret = 1;
        VIEWER_OPEN_ERROR(5,"Couldn't open chosen file.");
        goto viewer_open_error;
    }

    // Configure Touched Interface and PIT.
    TSD_InareaAdd(exit_pressed, exit_moved_out, exit_release, &exit_ret, 1);
    TSD_InareaAdd(v_pause, 0, v_play, picture, 2);
    PITD_Configure(_viewer_handler, 15000, 1);
    TSD_SetPitPeriod(15);

    // Start playback.
    _decret = 0;
    _selected = 0;
    state = _PLAY;
    PITD_Start();
    // Playback cycle.
    while (state != _EXIT) {
        // Decode the s*** out off that file!
        // Those shouldn't be hard-coded... But it dosen't matter.
        switch (decod_state) {
        case 1: {
            decod_return = DecodeOneFrame(&decod_unused);
            if (decod_return == DEC_EOS || decod_return == DEC_SUCCEED) {
                // TODO WTF?
                //process the decoded picture, output or display;
                decod_frames++;
            } else {
                _decret = 1;
                break;
            }
            if (!((decod_return == DEC_SUCCEED) && ((cfgparams.iDecFrmNum == 0)
                    || (decod_frames < cfgparams.iDecFrmNum)))) {
                decod_state = 2;
            }
            break;
        }
        case 2: {
            decod_return = FinitDecoder(&decod_unused);
            if (decod_return != DEC_SUCCEED) {
                _decret = 1;
                break;
            }
            decod_state = 3;
            break;
        }
        case 3: {
            while (put_frame == curr_frame) {
                if (state == _EXIT) {
                    goto away;
                }
            }
            // extra black frame
            LCDD_Fill(*put_frame, COLOR_BLACK);
            last_frame = put_frame;
            // wait until the buffer is empty
            on_hold = 1;
            while (curr_frame != last_frame && state != _EXIT)
                ;
            // go away
            goto away;
        }
        }
        // check for errors
        if (_decret) {
            VIEWER_OPEN_ERROR(23,"The video is corrupted.");
            break;
        }
    } away:
    // End playback.
    PITD_Stop();
    TSD_InareaRemoveAll();

    CloseDecoder();

    if (_decret)
        goto viewer_open_error;

    // Exit viewer.
    viewer_open_exit: LCDD_DisplayBuffer(oldBuff);
    return 0;

    // Handle error.
    viewer_open_error: LCDD_DrawString(curr_frame, error_x, 95, error_str,
            COLOR_RED);
    {
        inarea_rect_t screen = { .x = 0, .y = 0, .width = BOARD_LCD_WIDTH,
                .height = BOARD_LCD_HEIGHT };
        TSD_InareaAdd(0, 0, _error_func, &screen, 1);
        PITD_Configure(TSD_Update_PenState, 10000, 1);
        TSD_SetPitPeriod(10);
        LCDD_DrawString(curr_frame, 107, 128, "Touch to go back.", COLOR_YELLOW);
        // Wait for touch.
        PITD_Start();
        while (_decret)
            ;
        PITD_Stop();
        TSD_InareaRemoveAll();
    }
    goto viewer_open_exit;
}

void viewer_init(void)
{
    int f;
    for (f = 0; f < _NUMOF_FRAMES; ++f) {
        frames[f] = _fremas + f * BOARD_LCD_WIDTH * BOARD_LCD_HEIGHT * 3;
    }
}

/*******************************************************************************
 * Third Party
 */

/*
 * The code bellow is an adaptation of a code found at C. Matthew Curtin's
 * page form the Department of Computer Science and Engineering at The Ohio
 * State University.
 *
 * Only the function that converts Y'UV 4:2:0 to RGB 24b was kept.
 *
 * The original code did not rotate the image nor blacked the unused screen,
 * both applications are specific for the program using this conversion.
 *
 * The following block comment was untouched from the original .c file.
 */
/*
 * yuv2rgb.c, Software YUV to RGB coverter
 *
 *  Copyright (C) 1999, Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *  All Rights Reserved.
 *
 *  Functions broken out from display_x11.c and several new modes
 *  added by H�kan Hjort <d95hjort@dtek.chalmers.se>
 *
 *  15 & 16 bpp support by Franck Sicard <Franck.Sicard@solsoft.fr>
 *
 *  This file is part of mpeg2dec, a free MPEG-2 video decoder
 *
 *  mpeg2dec is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  mpeg2dec is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* matrix coefficients */
#define CRV     117504
#define CBU     138453
#define CGU     13954
#define CGV     34903

static unsigned char clip(int y, int common)
{
    int i = (y + common) >> 16;
    return (unsigned char) (i < 0 ? 0 : (i > 255 ? 255 : i));
}

static void yuv2rgb_with_rotation(unsigned char* image,
                                  const unsigned char* py,
                                  const unsigned char* pu,
                                  const unsigned char* pv,
                                  const int width,
                                  const int height,
                                  const int y_stride,
                                  const int uv_stride)
{
    int y, u, v;
    int r_common, g_common, b_common;
    int i, j;

    int posX = 0, posY;
    unsigned char *odd, *even;

    const unsigned char* py_line_1;
    const unsigned char* py_line_2;

    py_line_1 = py;
    py_line_2 = py + y_stride;

    for (j = 0, posY = -2; j < height / 2; j++) {
        posY += 2;
        for (i = 0, posX = BOARD_LCD_WIDTH; i < width / 2; i++) {
            posX -= 2;

            //Common to all four pixels
            u = (*pu++) - 128;
            v = (*pv++) - 128;

            r_common = CRV * v + 32768;
            g_common = CGU * u + CGV * v - 32768;
            b_common = CBU * u + 32768;

            even = image + 3 * (posX * BOARD_LCD_HEIGHT + posY);
            odd = even + 3 * BOARD_LCD_HEIGHT;

            //Pixel I
            y = 76309 * ((*py_line_1++) - 16);
            *odd++ = clip(y, r_common);
            *odd++ = clip(y, -g_common);
            *odd++ = clip(y, b_common);

            //Pixel II
            y = 76309 * ((*py_line_1++) - 16);
            *even++ = clip(y, r_common);
            *even++ = clip(y, -g_common);
            *even++ = clip(y, b_common);

            //Pixel III
            y = 76309 * ((*py_line_2++) - 16);
            *odd++ = clip(y, r_common);
            *odd++ = clip(y, -g_common);
            *odd = clip(y, b_common);

            //Pixel IV
            y = 76309 * ((*py_line_2++) - 16);
            *even++ = clip(y, r_common);
            *even++ = clip(y, -g_common);
            *even = clip(y, b_common);
        }

        py_line_1 += y_stride;
        py_line_2 += y_stride;
        pu += uv_stride - width / 2;
        pv += uv_stride - width / 2;
    }

    // Black those out-of-bounds pixels:
    if (posY < BOARD_LCD_HEIGHT) { // bottom
        for (i = 0; i < BOARD_LCD_WIDTH; ++i) {
            odd = image + 3 * (i * BOARD_LCD_HEIGHT + posY);
            for (j = posY; j < BOARD_LCD_HEIGHT; ++j) {
                *odd++ = 0;
                *odd++ = 0;
                *odd++ = 0;
            }
        }
    }
    if (posX) { // right
        for (i = 0; i < posX; ++i) {
            odd = image + 3 * i * BOARD_LCD_HEIGHT;
            for (j = 0; j < posY; ++j) {
                *odd++ = 0;
                *odd++ = 0;
                *odd++ = 0;
            }
        }
    }
}
